#include "checkers/FrequentProcessSwitchChecker.h"

extern ReportSpace::Report external_bug_report;
class OSAPIVisiter : public clang::ASTConsumer, public clang::RecursiveASTVisitor<OSAPIVisiter>
{
private:
    ASTContext *context;
    SourceManager *sm;
    ASTManager *manager;
    ASTResource *resource;

    unordered_map<FunctionDecl *, vector<OSAPI>> env;
    set<string> osList;
    vector<Warning> report;
    int threshold;
    int limit;

public:
    OSAPIVisiter(ASTContext *astcontext, ASTManager *astmanager, ASTResource *astresource, set<string> list1, int limit1, int threshold1) : context(astcontext), manager(astmanager), resource(astresource), osList(list1), limit(limit1), threshold(threshold1)
    {
        sm = &(context->getSourceManager());
    }

    vector<Warning> &getReport()
    {
        return report;
    }

    void HandleTranslationUnit(clang::ASTContext &Context)
    {
        TraverseDecl(Context.getTranslationUnitDecl());
    }

    void HandleFunctionDecl(FunctionDecl *F)
    {
        TraverseDecl(F);
    }

    bool VisitStmt(Stmt *st)
    {
        for (auto &iter : env)
        {
            for (auto &it : iter.second)
            {
                it.visitNum++;
            }
        }
        return true;
    }

    bool VisitForStmt(ForStmt *fs)
    {
        clear();

        return true;
    }

    bool VisitWhileStmt(WhileStmt *ws)
    {
        clear();

        return true;
    }

    bool VisitDoStmt(DoStmt *ds)
    {
        clear();

        return true;
    }

    bool VisitCallExpr(CallExpr *CE)
    {
        if (CE == nullptr)
        {
            return true;
        }
        FunctionDecl *FD = CE->getDirectCallee();
        if (FD == nullptr || osList.find(FD->getNameAsString()) == osList.end())
        {
            return true;
        }
        fileLoc loc = getLocFromString(CE->getBeginLoc().printToString(*sm));
        unsigned line = loc.line;
        unsigned col = loc.col;
        OSAPI api = {line, col, 0, loc.name};
        if (env.find(FD) == env.end())
        {
            vector<OSAPI> tmp;
            tmp.push_back(api);
            env[FD] = tmp;
            return true;
        }

        vector<OSAPI> &v = env[FD];

        v.push_back(api);
        if (v.size() == limit)
        {
            if (v.front().visitNum <= threshold)
            {
                Warning w = {
                    FD,
                    v.front().line,
                    v.front().col,
                    line,
                    col,
                    v.front().visitNum,
                    loc.name};

                report.push_back(w);
            }
            v.erase(v.begin());
            while (!v.empty())
            {
                if (v.front().visitNum > threshold)
                {
                    v.erase(v.begin());
                    continue;
                }
                break;
            }
            if (v.empty())
            {
                env.erase(env.find(FD));
            }
        }
        return true;
    }

private:
    void clear()
    {
        for (auto &iter : env)
        {
            for (auto &it : iter.second)
            {
                it.visitNum += (threshold + 1);
            }
        }
    }
    struct fileLoc
    {
        string name;
        unsigned line;
        unsigned col;
    };
    fileLoc getLocFromString(string fileName)
    {
        fileLoc loc;
        size_t pos = fileName.find(":");
        loc.name = fileName.substr(0, pos);
        string tmp = fileName.substr(pos + 1);
        pos = tmp.find(":");
        string lineStr = tmp.substr(0, pos);
        string tmp2 = tmp.substr(pos + 1);
        pos = tmp2.find(" ");
        string colStr = tmp2.substr(0, pos);
        loc.line = (unsigned)stoi(lineStr);
        loc.col = (unsigned)stoi(colStr);
        return loc;
    }
};

class OSAPIVisiterHelper : public clang::ASTConsumer, public clang::RecursiveASTVisitor<OSAPIVisiterHelper>
{
private:
    ASTContext *context;
    SourceManager *sm;
    ASTManager *manager;
    ASTResource *resource;

    unordered_map<FunctionDecl *, vector<OSAPI>> env;
    set<string> osList;
    vector<Warning> report;
    int threshold;
    int limit;

public:
    OSAPIVisiterHelper(ASTContext *astcontext, ASTManager *astmanager, ASTResource *astresource, set<string> list1, int limit1, int threshold1) : context(astcontext), manager(astmanager), resource(astresource), osList(list1), limit(limit1), threshold(threshold1)
    {
        sm = &(context->getSourceManager());
    }
    void HandleTranslationUnit(clang::ASTContext &Context)
    {
        TraverseDecl(Context.getTranslationUnitDecl());
    }
    bool VisitFunctionDecl(FunctionDecl *F)
    {
        OSAPIVisiter visiter(context, manager, resource, osList, limit, threshold);
        visiter.HandleFunctionDecl(F);
        vector<Warning> &info = visiter.getReport();
        report.insert(report.end(), info.begin(), info.end());
        return true;
    }
    vector<Warning> &getReport()
    {
        return report;
    }
};

void FrequentProcessSwitchChecker::check()
{
    readConfig();
    //getEntryFunc();
    analyse();
}
void FrequentProcessSwitchChecker::readConfig()
{
    std::unordered_map<std::string, std::string> ptrConfig = configure->getOptionBlock("ProcessSwitchingFrequentlyChecker");
    limit = stoi(ptrConfig.find("limit")->second);
    threshold = stoi(ptrConfig.find("threshold")->second);
    funcs = parse(ptrConfig.find("func")->second);
}

vector<string> FrequentProcessSwitchChecker::parse(string funcs)
{
    vector<string> tmp;
    int param;
    int pos_begin = 0;
    int pos_end = 0;
    int size = funcs.size();
    string func_param;
    pos_end = funcs.find(":");
    while (pos_end != string::npos)
    {
        func_param = funcs.substr(0, pos_end);
        funcs = funcs.substr(pos_end + 1);
        pos_end = funcs.find(":");
        tmp.push_back(func_param);
    }
    return tmp;
}

void FrequentProcessSwitchChecker::getEntryFunc()
{
    std::vector<ASTFunction *> topLevelFuncs = call_graph->getTopLevelFunctions();
    for (auto fun : topLevelFuncs)
    {
        const FunctionDecl *funDecl = manager->getFunctionDecl(fun);
        if (funDecl->getQualifiedNameAsString() == "main")
        {
            entryFunc = fun;
            return;
        }
    }
    entryFunc = nullptr;
    return;
}

void FrequentProcessSwitchChecker::analyse()
{
    vector<Warning> report;
    set<string> list1;
    for (auto func : funcs)
    {
        list1.insert(func);
    }
    std::vector<ASTFile *> astFiles = resource->getASTFiles();
    int count = 1;
    for (ASTFile *astFile : astFiles)
    {
        process_bar(float(count++) / astFiles.size());
        ASTUnit *astUnit = manager->getASTUnit(astFile);
        ASTContext &context = astUnit->getASTContext();
        OSAPIVisiterHelper visiter(&context, manager, resource, list1, limit, threshold);
        visiter.HandleTranslationUnit(context);
        vector<Warning> &info = visiter.getReport();
        report.insert(report.end(), info.begin(), info.end());
    }
    int i = 1;
    for (auto it : report)
    {
        //it.report();

        string desc = "";
        string fileName = it.fileName;
        unsigned line = it.beginLine;
        unsigned col = it.beginCol;
        desc += "from " + to_string(it.beginLine) + " to " + to_string(it.endLine);

        ReportSpace::Defect *def = new ReportSpace::Defect(ReportSpace::DefectName::ProcessSwitchingFrequently,
                                                           ReportSpace::DefectType::Warning,
                                                           fileName, line, col,
                                                           "");
        def->addDesc(desc);
        external_bug_report.addToReport(def);
    }
}